package pfc.pfcjoc.core;

import static playn.core.PlayN.graphics;
import static playn.core.PlayN.keyboard;
import static playn.core.PlayN.log;
import static playn.core.PlayN.pointer;

import org.jbox2d.callbacks.ContactImpulse;
import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.callbacks.DebugDraw;
import org.jbox2d.collision.Manifold;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.Contact;

import pfc.pfcjoc.entities.*;
import playn.core.CanvasImage;
import playn.core.DebugDrawBox2D;
import playn.core.Font;
import playn.core.ImageLayer;
import playn.core.Keyboard;
import playn.core.PlayN;
import playn.core.Pointer;
import playn.core.ResourceCallback;
import playn.core.Sound;
import tripleplay.ui.Background;
import tripleplay.ui.Interface;
import tripleplay.ui.Label;
import tripleplay.ui.Root;
import tripleplay.ui.Style;
import tripleplay.ui.Style.TextEffect;
import tripleplay.ui.Styles;
import tripleplay.ui.Stylesheet;
import tripleplay.ui.layout.AxisLayout;

public class PlayGameState extends GameState implements ContactListener {
  private static final PlayGameState instance = new PlayGameState();

  private PlayLevel level;
  private Player player;
  private World world;

  private float scrollX = 0;
  private float prevScrollX = 0;

  // private float scrollY;

  private static boolean showDebugDraw = false;
  private DebugDrawBox2D debugDraw;
  private ImageLayer debugCanvasLayer;
  private CanvasImage debugCanvasImage;

  private Keyboard.Listener keyboard = null;
  private Pointer.Listener pointer = null;

  private final int PLAY = 1;
  private final int PAUSE = 2;
  private final int LEVELEND = 3;
  private final int GAMEOVER = 4;
  private int mode;

  private Interface iface;
  private Root topBarRoot;
  private Root gameOverRoot;
  private Root pauseRoot;
  private Root levelEndRoot;

  private Label videsLabel;
  private Label puntsLabel;
  private Label tempsLabel;

  private int vides;
  private int punts;
  private int temps;

  private Sound pauseSound;
  private Sound bgMusicIntroSound;
  private Sound bgMusicLoopSound;
  private Sound stompSound;

  private boolean introSoundStarted = false;
  private boolean introSoundPlayed = false;


  // private Label gameOverLabel;
  // private Label pauseLabel;
  // private Label levelEndLabel;

  // private float scrollX = 0;

  private PlayGameState() {
    // Inicialització Box2D
    Boolean doSleep = true;
    Vec2 gravity = new Vec2(0.0f, 10.0f);
    world = new World(gravity, doSleep);
    world.setWarmStarting(true);
    world.setAutoClearForces(true);
    world.setContactListener(this);

    // Init variables
    level = new PlayLevel(world);
    loadSound();

    setControlListeners();
  }


  public static PlayGameState getInstance() {
    return instance;
  }


  private void addGraphics() {
    graphics().rootLayer().add(level.getLevelLayer());
    graphics().rootLayer().add(level.getBgColorLayer());

    if (showDebugDraw) {
      enableDebugDraw();
    }
  }


  private void removeGraphics() {
    graphics().rootLayer().remove(level.getLevelLayer());
    graphics().rootLayer().remove(level.getBgColorLayer());

    if (showDebugDraw) {
      disableDebugDraw();
    }
  }


  private void enableDebugDraw() {
    graphics().rootLayer().add(debugCanvasLayer);
    world.setDebugDraw(debugDraw);
  }


  private void disableDebugDraw() {
    graphics().rootLayer().remove(debugCanvasLayer);
    world.setDebugDraw(null);
  }


  public void onEnteringMode() {
    log().info("Entrant a PLAYING");

    punts = 0;
    vides = 3;
    temps = 0;

    level = new PlayLevel(world);
    level.loadLevel(new ResourceCallback<Boolean>() {
      @Override
      public void done(Boolean res) {
        // Init of debug drawing
        debugCanvasImage = graphics().createImage(graphics().width(), graphics().height());
        debugCanvasLayer = graphics().createImageLayer(debugCanvasImage);
        debugDraw = new DebugDrawBox2D();
        debugDraw.setCanvas(debugCanvasImage);
        debugDraw.setFlipY(false);
        debugDraw.setStrokeAlpha(150);
        debugDraw.setFillAlpha(75);
        debugDraw.setStrokeWidth(2.0f);
        debugDraw.setFlags(DebugDraw.e_shapeBit | DebugDraw.e_jointBit | DebugDraw.e_aabbBit);
        debugDraw.setCamera(0, 0, pfcjoc.toPix(1f));

        player = level.getPlayer();
        scrollX = 0;
        addGraphics();
        setControlListeners();
        configInterface();
      }


      @Override
      public void error(Throwable err) {
        log().error("Error loading LEVEL: " + err.getMessage());
      }
    });

  }


  public void onLeavingMode() {
    keyboard().setListener(null);
    // Netejem el nivell
    level.removeAll();
    // Eliminem capes gràfiques de joc
    removeGraphics();
    // Netejem UI
    topBarRoot.destroyAll();
    gameOverRoot.destroyAll();
    pauseRoot.destroyAll();
    levelEndRoot.destroyAll();
    // Deixem de reproduir la musica
    bgMusicIntroSound.stop();
    bgMusicLoopSound.stop();
    introSoundStarted = false;
    introSoundPlayed = false;
  }


  // public void enterEditingMode() {
  // EditGameState edit = EditGameState.getInstance();
  // edit.setContext(context);
  // context.setCurrentGameState(edit);
  // }

  public void enterPlayingMode() {
    log().info("Ja estem en PLAYING");
  }


  public void enterMenuMode() {
    MenuGameState menu = MenuGameState.getInstance();
    context.setCurrentGameState(menu);
  }


  private void loadSound() {
    pauseSound = PlayN.assets().getSound("audio/pause");
    pauseSound.addCallback(new ResourceCallback<Sound>() {
      @Override
      public void done(Sound sound) {
        log().info("Sound loaded!");
      }


      @Override
      public void error(Throwable err) {
        log().error("Error loading sound!", err);
      }
    });

    bgMusicIntroSound = PlayN.assets().getSound("audio/level1intro");
    bgMusicIntroSound.addCallback(new ResourceCallback<Sound>() {
      @Override
      public void done(Sound sound) {
        log().info("Sound loaded!");
      }


      @Override
      public void error(Throwable err) {
        log().error("Error loading sound!", err);
      }
    });

    bgMusicLoopSound = PlayN.assets().getSound("audio/level1bucle");
    bgMusicLoopSound.addCallback(new ResourceCallback<Sound>() {
      @Override
      public void done(Sound sound) {
        log().info("Sound loaded!");
      }


      @Override
      public void error(Throwable err) {
        log().error("Error loading sound!", err);
      }
    });
    
    stompSound = PlayN.assets().getSound("audio/stomp");
    stompSound.addCallback(new ResourceCallback<Sound>() {
      @Override
      public void done(Sound sound) {
        log().info("Sound loaded!");
      }


      @Override
      public void error(Throwable err) {
        log().error("Error loading sound!", err);
      }
    });
  }


  public void setControlListeners() {
    // MOUSE POINTER
    if (pointer == null) {
      pointer = new Pointer.Adapter() {
        @Override
        public void onPointerStart(Pointer.Event event) {
        }


        @Override
        public void onPointerDrag(Pointer.Event event) {
        }


        @Override
        public void onPointerEnd(Pointer.Event event) {
        }
      };
    }
    pointer().setListener(pointer);

    // KEYBOARD
    if (keyboard == null) {
      keyboard = new Keyboard.Adapter() {
        @Override
        public void onKeyDown(Keyboard.Event event) {
          switch (event.key()) {
            case SPACE:
              player.setControlUp(true);
              break;
            case DOWN:
              player.setControlDown(true);
              break;
            case RIGHT:
              player.setControlRight(true);
              break;
            case LEFT:
              player.setControlLeft(true);
              break;
          }
        }


        @Override
        public void onKeyUp(Keyboard.Event event) {
          switch (event.key()) {
            case SPACE:
              player.setControlUp(false);
              break;
            case DOWN:
              player.setControlDown(false);
              break;
            case RIGHT:
              player.setControlRight(false);
              break;
            case LEFT:
              player.setControlLeft(false);
              break;
            case ESCAPE:
              enterMenuMode();
              break;
            case P:
              switch (mode) {
                case PLAY:
                  changeToMode(PAUSE);
                  if (pauseSound.isPlaying()) {
                    pauseSound.stop();
                  }
                  if (!pauseSound.play()) {
                    log().info("Error al reproduir el so pause.mp3");
                  }
                  break;
                case PAUSE:
                  changeToMode(PLAY);
                  break;
              }
              break;
            case ENTER:
              switch (mode) {
                case GAMEOVER:
                  changeToMode(PLAY);
                  onLeavingMode();
                  onEnteringMode();
                  break;
                case LEVELEND:
                  changeToMode(PLAY);
                  onLeavingMode();
                  onEnteringMode();
                  break;
              }
              break;
          }
        }
      };
    }
    keyboard().setListener(keyboard);
  }


  private void configInterface() {
    // Mode per defecte al entrar a joc
    mode = PLAY;

    if (iface == null) {
      iface = new Interface();
    }

    Stylesheet rootSheet = Stylesheet.builder().create();
    Styles titleStyles = Styles.none().add(Style.FONT.is(PlayN.graphics().createFont("Helvetica", Font.Style.BOLD, 48))).add(
        Style.COLOR.is(0xFF222222)).add(Style.TEXT_EFFECT.is(TextEffect.OUTLINE));
    Styles subTitleStyles = Styles.none().add(Style.FONT.is(PlayN.graphics().createFont("Helvetica", Font.Style.BOLD, 20))).add(
        Style.COLOR.is(0xFF222222)).add(Style.TEXT_EFFECT.is(TextEffect.OUTLINE));
    Styles rootStyles = Styles.make(Style.BACKGROUND.is(Background.bordered(0x88BBBBBB, 0xFF000000, 2)));
    Styles topBarStyles = Styles.make(Style.BACKGROUND.is(Background.blank())).add(
        Style.FONT.is(PlayN.graphics().createFont("Helvetica", Font.Style.BOLD, 20))).add(Style.TEXT_EFFECT.is(TextEffect.OUTLINE)).add(
        Style.HIGHLIGHT.is(0xFF000000)).add(Style.COLOR.is(0xCCFFFFFF));

    // TOP BAR: barra info: vides, punts, temps...
    topBarRoot = iface.createRoot(AxisLayout.horizontal().gap(50), rootSheet);
    topBarRoot.setSize(graphics().width(), graphics().height() / 12);
    topBarRoot.addStyles(topBarStyles);
    graphics().rootLayer().add(topBarRoot.layer);

    videsLabel = new Label("Vides: " + vides).addStyles(topBarStyles);
    puntsLabel = new Label("Punts: " + punts).addStyles(topBarStyles);
    tempsLabel = new Label("Temps: " + temps).addStyles(topBarStyles);
    topBarRoot.add(videsLabel, puntsLabel, tempsLabel);

    // GAME OVER info
    gameOverRoot = iface.createRoot(AxisLayout.vertical().gap(50), rootSheet);
    gameOverRoot.setBounds(graphics().width() / 4, graphics().height() / 4, graphics().width() / 2, graphics().height() / 2);
    gameOverRoot.addStyles(rootStyles);

    Label gameOverLabel = new Label("GAME OVER");
    Label gameOverSubLabel = new Label("Prem \"ENTER\" per tornar a jugar");
    gameOverLabel.addStyles(titleStyles);
    gameOverSubLabel.addStyles(subTitleStyles);
    gameOverRoot.add(gameOverLabel);
    gameOverRoot.add(gameOverSubLabel);

    // PAUSE info
    pauseRoot = iface.createRoot(AxisLayout.vertical().gap(50), rootSheet);
    pauseRoot.setBounds(graphics().width() / 4, graphics().height() / 4, graphics().width() / 2, graphics().height() / 2);
    pauseRoot.addStyles(rootStyles);

    Label pauseLabel = new Label("PAUSE");
    Label pauseSubLabel = new Label("Prem \"P\" per seguir jugant");
    pauseLabel.addStyles(titleStyles);
    pauseSubLabel.addStyles(subTitleStyles);
    pauseRoot.add(pauseLabel);
    pauseRoot.add(pauseSubLabel);

    // ENDLEVEL info
    levelEndRoot = iface.createRoot(AxisLayout.vertical().gap(50), rootSheet);
    levelEndRoot.setBounds(graphics().width() / 6, graphics().height() / 4, graphics().width() / 1.5f, graphics().height() / 2);
    levelEndRoot.addStyles(rootStyles);

    Label levelEndLabel = new Label("NIVELL COMPLET!");
    Label levelEndSubLabel = new Label("Prem \"ENTER\" per tornar a jugar");
    levelEndLabel.addStyles(titleStyles);
    levelEndSubLabel.addStyles(subTitleStyles);
    levelEndRoot.add(levelEndLabel);
    levelEndRoot.add(levelEndSubLabel);
  }


  private void calculateNewScroll() {
    float xp = pfcjoc.toPix(player.getPosPhy().x);

    if (xp > (pfcjoc.width / 2) && xp < (pfcjoc.toPix(level.getWidth()) - (pfcjoc.width / 2))) {
      scrollX = -(xp - (pfcjoc.width / 2));
    }
    else if (xp <= (pfcjoc.width / 2)) {
      scrollX = 0;
    }
    else if (xp >= (pfcjoc.toPix(level.getWidth()) - (pfcjoc.width / 2))) {
      scrollX = -((pfcjoc.toPix(level.getWidth()) - (pfcjoc.width / 2)) - (pfcjoc.width / 2));
    }
    // Exemple: world amb width = 1600
    // if (xp > 400 && xp < 1200) {
    // scrollX = -(xp - 400);
    // }
    // else if (xp <= 400) {
    // scrollX = 0;
    // }
    // else if (xp >= 1200) {
    // scrollX = -800;
    // }
    // log().info("scrollX: " + scrollX);
  }


  // SCROLLING = CAMERA SIMULATION
  private void scrollCamera(float alpha) {
    float sx = (scrollX * alpha) + (prevScrollX * (1f - alpha));
    level.getLevelLayer().setTranslation(sx, 0);
    // debugCanvasLayer.setTranslation(sx, 0);
    debugDraw.setCamera(pfcjoc.toPhy(-sx), 0, pfcjoc.toPix(1f));
  }


  public void paint(float alpha) {
    if (!level.isLoaded()) return;

    switch (mode) {
      case PLAY:
        scrollCamera(alpha);
        // PAINTING
        level.paint(alpha);

        // DEBUG DRAWING
        if (showDebugDraw) {
          // Debug Layer s'ha de pintar per damunt de TOT
          // debugCanvasLayer.setDepth(100);
          debugDraw.getCanvas().clear();
          world.drawDebugData();
        }

        break;

      case PAUSE:

        break;

      case LEVELEND:

        break;

      case GAMEOVER:

        break;
    }

    if (iface != null) {
      iface.paint(alpha);
    }

  }


  public void update(float delta) {
    if (!level.isLoaded()) return;

    switch (mode) {
      case PLAY:

        // guarda scroll anterior
        prevScrollX = scrollX;

        // calcula accions de player
        player.update(delta);
        level.update(delta);

        // player ha mort?
        if (player.isDead() || player.getPosPhy().y > level.getHeight() + pfcjoc.toPhy(player.getHeight())) {
          vides--;
          // game over?
          if (vides < 0) {
            vides = 0;
            changeToMode(GAMEOVER);
          }
          else {
            player.revive(level.getPlayerStartPos());
          }
        }

        // calcul del nou scroll
        calculateNewScroll();

        temps += delta;

        break;

      case PAUSE:

        break;

      case LEVELEND:

        break;

      case GAMEOVER:
        break;
    }

    if (iface != null) {
      iface.update(delta);

      // videsLabel.text.update("Vides: " + String.format("%1$02d", vides));
      // puntsLabel.text.update("Punts: " + String.format("%1$06d", punts));
      // tempsLabel.text.update("Temps: " + String.format("%1$03d", temps / 1000));

      videsLabel.text.update("Vides: " + vides);
      puntsLabel.text.update("Punts: " + punts);
      tempsLabel.text.update("Temps: " + temps / 1000);
    }

    playAudio();
  }


  private void playAudio() {
    // música level sonant
    if (!introSoundPlayed) {
      if (!introSoundStarted) {
        if (!bgMusicIntroSound.play()) {
          log().info("Error al reproduir el so pause.mp3");
        }
        introSoundStarted = true;
      }
      else {
        if (!bgMusicIntroSound.isPlaying()) {
          introSoundPlayed = true;
        }
      }
    }
    else {
      if (!bgMusicLoopSound.isPlaying()) {
        if (!bgMusicLoopSound.play()) {
          log().info("Error al reproduir el so pause.mp3");
        }
        bgMusicLoopSound.setLooping(true);
      }
    }

  }


  private void changeToMode(int newMode) {
    // Mode actual
    switch (mode) {
      case PLAY:

        break;

      case PAUSE:
        graphics().rootLayer().remove(pauseRoot.layer);
        break;

      case LEVELEND:
        graphics().rootLayer().remove(levelEndRoot.layer);
        break;

      case GAMEOVER:
        graphics().rootLayer().remove(gameOverRoot.layer);
        break;
    }

    // Nou mode
    switch (newMode) {
      case PLAY:

        break;

      case PAUSE:
        graphics().rootLayer().add(pauseRoot.layer);
        break;

      case LEVELEND:
        graphics().rootLayer().add(levelEndRoot.layer);
        break;

      case GAMEOVER:
        graphics().rootLayer().add(gameOverRoot.layer);
        break;
    }

    mode = newMode;
  }


  @Override
  public void beginContact(Contact contact) {
    Fixture fixtureA = contact.getFixtureA();
    Fixture fixtureB = contact.getFixtureB();

    manageBeginContact(fixtureA, fixtureB);
    manageBeginContact(fixtureB, fixtureA);
  }


  @Override
  public void endContact(Contact contact) {
    Fixture fixtureA = contact.getFixtureA();
    Fixture fixtureB = contact.getFixtureB();

    manageEndContact(fixtureA, fixtureB);
    manageEndContact(fixtureB, fixtureA);
  }


  @Override
  public void preSolve(Contact contact, Manifold oldManifold) {

  }


  @Override
  public void postSolve(Contact contact, ContactImpulse impulse) {

  }


  private void manageBeginContact(Fixture firstFxt, Fixture secondFxt) {
    Object userDataFirstFxt = firstFxt.getUserData();
    Object userDataSecondFxt = secondFxt.getUserData();

    if (userDataFirstFxt != null) {
      if (((String) userDataFirstFxt).equals("endBlock")) {
        if (userDataSecondFxt != null) {
          Element elemTouched = (Element) secondFxt.getBody().getUserData();
          if (elemTouched.getName().equals("player")) {
            changeToMode(LEVELEND);
          }
        }
      }
      if (((String) userDataFirstFxt).equals("sensorPeus")) {
        player.numFootContacts = player.numFootContacts + 1;

        // Element elemTouched = (Element) secondFxt.getBody().getUserData();
        // if (elemTouched.getName().equals("toad")) {
        if (userDataSecondFxt != null) {
          if (((String) userDataSecondFxt).equals("sensorCapToad")) {
            log().info(" ==== colisio peusplayer amb capenemic");
            player.numFootContactsOnEnemy = player.numFootContactsOnEnemy + 1;
            Element elemTouched = (Element) secondFxt.getBody().getUserData();
            level.remove(elemTouched);
            punts += 100;
            
            if (stompSound.isPlaying()) {
              stompSound.stop();
            }
            if (!stompSound.play()) {
              log().info("Error al reproduir el so stomp.mp3");
            }
          }
        }
      }
      if (((String) userDataFirstFxt).equals("sensorEsqToad")) {
        Toad toad = (Toad) firstFxt.getBody().getUserData();
        toad.setNumEsqContacts(toad.getNumEsqContacts() + 1);

        // TODO: player toca toad (enemy)
        if (userDataSecondFxt != null) {
          Element elemTouched = (Element) secondFxt.getBody().getUserData();
          if (elemTouched.getName().equals("player") && !((String) userDataSecondFxt).equals("sensorPeus")) {
            log().info(" ++++ colisio esqToad amb player");
            player.numBodyContactsOnEnemy = player.numBodyContactsOnEnemy + 1;
          }
        }
      }
      if (((String) userDataFirstFxt).equals("sensorDretaToad")) {
        Toad toad = (Toad) firstFxt.getBody().getUserData();
        toad.setNumDretaContacts(toad.getNumDretaContacts() + 1);

        if (userDataSecondFxt != null) {
          Element elemTouched = (Element) secondFxt.getBody().getUserData();
          if (elemTouched.getName().equals("player") && !((String) userDataSecondFxt).equals("sensorPeus")) {
            log().info(" ++++ colisio dretaToad amb player");
            player.numBodyContactsOnEnemy = player.numBodyContactsOnEnemy + 1;
          }
        }
      }

      if (((String) userDataFirstFxt).equals("woodBlockFxt")) {
        WoodBlock woodblock = (WoodBlock) firstFxt.getBody().getUserData();
        if (userDataSecondFxt != null) {
          Element elemTouched = (Element) secondFxt.getBody().getUserData();
          if (elemTouched.getName().equals("player")) {
            player.numFootContactsOnPlatform++;
            player.setMovingPlatform(woodblock);
            // player.numFootContacts++;
          }
        }
      }

      // if (((String) userDataFirstFxt).equals("ramp")) {
      // player.setNumFootContactsOnRamp(player.getNumFootContactsOnRamp() + 1);
      // }
    }
  }


  private void manageEndContact(Fixture firstFxt, Fixture secondFxt) {
    Object userDataFirstFxt = firstFxt.getUserData();
    Object userDataSecondFxt = secondFxt.getUserData();

    if (userDataFirstFxt != null) {
      if (((String) userDataFirstFxt).equals("sensorPeus")) {
        player.numFootContacts = player.numFootContacts - 1;

        // Element elemTouched = (Element) secondFxt.getBody().getUserData();
        // if (elemTouched.getName().equals("toad")) {
        if (userDataSecondFxt != null) {
          if (((String) userDataSecondFxt).equals("sensorCapToad")) {
            player.numFootContactsOnEnemy = player.numFootContactsOnEnemy - 1;
          }
        }
      }
      if (((String) userDataFirstFxt).equals("sensorEsqToad")) {
        Toad toad = (Toad) firstFxt.getBody().getUserData();
        toad.setNumEsqContacts(toad.getNumEsqContacts() - 1);
      }
      if (((String) userDataFirstFxt).equals("sensorDretaToad")) {
        Toad toad = (Toad) firstFxt.getBody().getUserData();
        toad.setNumDretaContacts(toad.getNumDretaContacts() - 1);
      }
      // if (((String) userDataFirstFxt).equals("ramp")) {
      // player.setNumFootContactsOnRamp(player.getNumFootContactsOnRamp() - 1);
      // }

      if (((String) userDataFirstFxt).equals("woodBlockFxt")) {
        if (userDataSecondFxt != null) {
          Element elemTouched = (Element) secondFxt.getBody().getUserData();
          if (elemTouched.getName().equals("player")) {
            player.numFootContactsOnPlatform--;
            player.setMovingPlatform(null);
            // player.numFootContacts--;
          }
        }
      }
    }
  }
}
